using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using ICSharpCode.NRefactory.CSharp;
using Xunit;

namespace Framework.Tests.Refactoring
{
    public class AbstractDepthFirstAstVisitor : DepthFirstAstVisitor
    {
        internal static bool HasTypeDeclaration(AstNode astNode, string name)
        {
            var syntaxTree = (astNode is SyntaxTree) ? astNode : astNode.GetParent<SyntaxTree>();
            return syntaxTree.DescendantsAndSelf.OfType<TypeDeclaration>().Any(x => x.Name == name);
        }

        internal static IEnumerable<ParameterDeclaration> GetParametersFromArguments(params PrimitiveExpression[] arguments)
        {
            var counter = 0;
            foreach (var expression in arguments)
            {
                yield return new ParameterDeclaration(new PrimitiveType(expression.Value.GetType().Name),
                                             String.Format("p{0}", counter));
                counter++;
            }
        }

        internal static MethodDeclaration AddParameters(MethodDeclaration methodDeclaration, IEnumerable<ParameterDeclaration> parameters)
        {
            foreach (var parametersFromArgument in parameters)
            {
                methodDeclaration.Parameters.Add(parametersFromArgument);
            }

            return methodDeclaration;
        }

        internal static TypeDeclaration CreateInterface(string interfaceName)
        {
            var typeDeclaration = new TypeDeclaration
                {
                    ClassType = ClassType.Interface,
                    Modifiers = Modifiers.Public,
                    Name = interfaceName
                };

            return typeDeclaration;
        }

        internal static TypeDeclaration AddMethodToInterface(TypeDeclaration factoryInterface, AstType returnType, string methodeName,
                                          params ParameterDeclaration[] parameters)
        {
            var methodDeclaration = new MethodDeclaration
                {
                    ReturnType = returnType,
                    Name = methodeName,
                };

            AddParameters(methodDeclaration, parameters);

            factoryInterface.Members.Add(methodDeclaration);

            return factoryInterface;
        }

        protected static TypeDeclaration CreateClass(string className)
        {
            var implementation = new TypeDeclaration
                {
                    ClassType = ClassType.Class,
                    Modifiers = Modifiers.Public,
                    Name = className,
                };

            return implementation;
        }

        protected static NamespaceDeclaration CreateNamespace(string refactoritIocFactory)
        {
            return new NamespaceDeclaration(refactoritIocFactory);
        }

        protected static string GetFieldName(EntityDeclaration entityDeclaration)
        {
            return String.Format("{0}{1}", entityDeclaration.Name.First().ToString(CultureInfo.InvariantCulture).ToLowerInvariant(), entityDeclaration.Name.Substring(1));
        }

        protected static FieldDeclaration CreateDependencyField(TypeDeclaration targetType, TypeDeclaration fieldType, string fieldName)
        {
            var fieldDeclaration = new FieldDeclaration
                {
                    Modifiers = Modifiers.Private | Modifiers.Readonly,
                    ReturnType = fieldType.BaseTypes.First().Clone(),
                };

            fieldDeclaration.Variables.Add(new VariableInitializer(fieldName));

            targetType.Members.Add(fieldDeclaration);

            return fieldDeclaration;
        }

        internal static ConstructorDeclaration UpdateOrCreateConstructor(Modifiers modifiers, TypeDeclaration targetType, params ParameterDeclaration[] parameters)
        {
            var constructors = GetAllConstructors(targetType).Where(x => ContainsConstructor(parameters.ToList(), x) && x.Modifiers == modifiers).ToList();

            if (constructors.Any())
            {
                return constructors.First();
            }

            var constructor = new ConstructorDeclaration
                {
                    Modifiers = modifiers,
                    Name = targetType.Name,
                    Body = new BlockStatement()
                };

            targetType.Members.Add(constructor);

            foreach (var parameterDeclaration in parameters)
            {
                constructor.Parameters.Add(parameterDeclaration);

                var assignmentExpression = new AssignmentExpression
                    {
                        Left = new MemberReferenceExpression
                            {
                                Target = new ThisReferenceExpression(),
                                MemberName = parameterDeclaration.Name
                            },
                        Right = new IdentifierExpression(parameterDeclaration.Name)
                    };

                constructor.Body.Statements.Add(new ExpressionStatement(assignmentExpression));
            }

            return constructor;
        }

        private static bool ContainsConstructor(IReadOnlyList<ParameterDeclaration> expectedParameters, ConstructorDeclaration x)
        {
            var hasSameSignature = expectedParameters.Count == x.Parameters.Count;

            if (!hasSameSignature)
            {
                return false;
            }

            var actualParameters = x.Parameters.ToList();

            for (var i = 0; i < expectedParameters.Count; i++)
            {
                hasSameSignature = expectedParameters[i].Type.Equals(actualParameters[i].Type);

                if (!hasSameSignature)
                {
                    return false;
                }
            }

            return true;
        }

        private static IEnumerable<ConstructorDeclaration> GetAllConstructors(TypeDeclaration targetType)
        {
            return targetType.Descendants.OfType<ConstructorDeclaration>();
        }
    }

    // ReSharper disable InconsistentNaming
    // ReSharper disable UnusedMember.Global
    // ReSharper restore InconsistentNaming
    // ReSharper restore UnusedMember.Global

    // ReSharper disable InconsistentNaming
    // ReSharper disable UnusedMember.Global
    public class AbstractDepthFirstAstVisitorTest
    {
        //[Fact]
        //public void Should_return_existing_constructor_when_class_has_same_constructor_with_same_parameters_type()
        //{
        //    void classDeclaration = CreatePouetClass();
        //    var expectedContructor = new ConstructorDeclaration(Modifiers)
        //}

        [Fact(Skip = "Not Ready")]
        public void Should_find_existing_cohstructors()
        {
            var classDeclaration = CreatePouetClass();
            var expectectedConstructor = new ConstructorDeclaration { Modifiers = Modifiers.Public };
            expectectedConstructor.Parameters.Add(new ParameterDeclaration(new PrimitiveType("string"), "a"));

            classDeclaration.Members.Add(expectectedConstructor);

            var actualConstructor = AbstractDepthFirstAstVisitor.UpdateOrCreateConstructor(Modifiers.Public,
                                                                                           classDeclaration,
                                                                                           new ParameterDeclaration(
                                                                                               new PrimitiveType(
                                                                                                   "string"), "a"));
            Assert.Equal(expectectedConstructor, actualConstructor);
        }

        [Fact]
        public void Should_create_constructor_when_class_has_not_same_parameters_count()
        {
            var classDeclaration = CreatePouetClass();
            var notExpectedConstructor = new ConstructorDeclaration { Modifiers = Modifiers.Public };
            notExpectedConstructor.Parameters.Add(new ParameterDeclaration(new PrimitiveType("string"), "a"));
            notExpectedConstructor.Parameters.Add(new ParameterDeclaration(new PrimitiveType("string"), "b"));
            classDeclaration.Members.Add(notExpectedConstructor);

            var actualConstructor = AbstractDepthFirstAstVisitor.UpdateOrCreateConstructor(Modifiers.Public, classDeclaration, new ParameterDeclaration(new PrimitiveType("string"), "a"));

            Assert.NotEqual(notExpectedConstructor, actualConstructor);
        }

        [Fact]
        public void Should_create_constructor_when_class_has_not_same_parameters_types()
        {
            var classDeclaration = CreatePouetClass();
            var notExpectedConstructor = new ConstructorDeclaration { Modifiers = Modifiers.Public };
            notExpectedConstructor.Parameters.Add(new ParameterDeclaration(new PrimitiveType("string"), "a"));
            notExpectedConstructor.Parameters.Add(new ParameterDeclaration(new PrimitiveType("string"), "b"));
            classDeclaration.Members.Add(notExpectedConstructor);

            var actualConstructor = AbstractDepthFirstAstVisitor.UpdateOrCreateConstructor(Modifiers.Public, classDeclaration, new ParameterDeclaration(new PrimitiveType("string"), "a"), new ParameterDeclaration(new PrimitiveType("int"), "b"));

            Assert.NotEqual(notExpectedConstructor, actualConstructor);
        }

        [Fact]
        public void Should_create_constructor_when_class_has_hot_same_parameters_type()
        {
            var classDeclaration = CreatePouetClass();

            var notExpected = new ConstructorDeclaration { Modifiers = Modifiers.Public };

            var notExpectedParameter = new ParameterDeclaration(new SimpleType("string"), "pouet");

            notExpected.Parameters.Add(notExpectedParameter);

            classDeclaration.Members.Add(notExpected);

            var actualParameter = new ParameterDeclaration(new SimpleType("bool"), "pouet");
            var actual = AbstractDepthFirstAstVisitor.UpdateOrCreateConstructor(Modifiers.Public, classDeclaration, actualParameter);

            Assert.NotEqual(notExpected, actual);
        }

        [Fact]
        public void Should_create_constructor_when_class_has_not_constructor()
        {
            var classDeclaration = CreatePouetClass();

            var actual = AbstractDepthFirstAstVisitor.UpdateOrCreateConstructor(Modifiers.Public, classDeclaration);

            Assert.NotNull(actual);
            Assert.Equal(Modifiers.Public, actual.Modifiers);
            Assert.Equal(classDeclaration.Name, actual.Name);
        }

        [Fact]
        public void Should_get_existing_constructor()
        {
            var typeDeclaration = CreatePouetClass();
            var expected = new ConstructorDeclaration { Modifiers = Modifiers.Public };

            typeDeclaration.Members.Add(expected);
            var actual = AbstractDepthFirstAstVisitor.UpdateOrCreateConstructor(Modifiers.Public, typeDeclaration);

            Assert.Equal(expected, actual);
        }

        [Fact]
        public void Should_create_constructor_when_constructor_has_not_same_identity()
        {
            var classDeclaration = CreatePouetClass();
            var notExpected = new ConstructorDeclaration { Modifiers = Modifiers.Internal };

            classDeclaration.Members.Add(notExpected);

            var actual = AbstractDepthFirstAstVisitor.UpdateOrCreateConstructor(Modifiers.Public, classDeclaration);

            Assert.NotEqual(notExpected, actual);
            Assert.NotNull(actual);
        }

        [Fact]
        public void Should_get_existing_constructor_when_constructor_has_same_identity()
        {
            var classDeclaration = CreatePouetClass();
            var expected = new ConstructorDeclaration { Modifiers = Modifiers.Public };

            classDeclaration.Members.Add(expected);

            var actual = AbstractDepthFirstAstVisitor.UpdateOrCreateConstructor(Modifiers.Public, classDeclaration);

            Assert.Equal(expected, actual);
        }

        private static TypeDeclaration CreatePouetClass()
        {
            return new TypeDeclaration
                {
                    ClassType = ClassType.Class,
                    Name = "Pouet"
                };
        }

        [Fact]
        public void Should_return_parameter_from_argument()
        {
            var args = new[]
                {
                    new PrimitiveExpression("s", "string")
                };
            var actual = AbstractDepthFirstAstVisitor.GetParametersFromArguments(args).ToList();

            Assert.Equal(1, actual.Count());

            var firstParameter = actual.First();

            Assert.Equal("p0", firstParameter.Name);
            var primitiveType = (PrimitiveType)firstParameter.Type;
            Assert.Equal("String", primitiveType.Keyword);
        }

        [Fact]
        public void Should_return_interface()
        {
            const string expected = "toto";
            var syntaxTree = new SyntaxTree();

            var typeDeclaration = new TypeDeclaration { Name = expected };
            syntaxTree.Members.Add(typeDeclaration);

            var interfaceDeclaration = AbstractDepthFirstAstVisitor.CreateInterface(expected);

            Assert.Equal(expected, interfaceDeclaration.Name);
            Assert.Equal(ClassType.Interface, interfaceDeclaration.ClassType);
            Assert.Equal(Modifiers.Public, interfaceDeclaration.Modifiers);
            Assert.True(AbstractDepthFirstAstVisitor.HasTypeDeclaration(typeDeclaration, interfaceDeclaration.Name));
        }

        [Fact]
        public void Should_return_false_when_type_does_not_exists()
        {
            var syntaxTree = new SyntaxTree();

            Assert.False(AbstractDepthFirstAstVisitor.HasTypeDeclaration(syntaxTree, "pouet"));
        }

        [Fact]
        public void Should_return_true_when_type_does_not_exists()
        {
            const string expected = "pouet";

            var syntaxTree = new SyntaxTree();

            syntaxTree.Members.Add(new TypeDeclaration { Name = expected });

            Assert.True(AbstractDepthFirstAstVisitor.HasTypeDeclaration(syntaxTree, expected));
        }
    }
    // ReSharper restore UnusedMember.Global
    // ReSharper restore InconsistentNaming
}